-module(xbt_tcp_server).

-export([start/4,stop/2,info_connections/2]).

-define(BACKLOG, 512).
-include("xbt.hrl").

start(Ip,Port,ParseFun,MaxConns) when is_integer(Port) ->
    ServerName = from_ip_port(Ip,Port),
    case whereis(ServerName) of
        undefined    ->
            Master = self(),
            Pid  = spawn_link(fun() -> 
                                  start_server(Master,Ip,Port,ParseFun,MaxConns)
                              end),
            receive
                {ok, Pid}   ->
                    register(ServerName,Pid),
                    {ok, Pid};
                {Error,Pid} ->
                    Error
            end;
        Pid         ->
            {ok, Pid}
    end. 

stop(Ip,Port) when is_integer(Port)                    ->
    ServerName = from_ip_port(Ip,Port),
    case whereis(ServerName) of
        undefined       ->
            {error,notstarted};
        Pid             ->
            exit(Pid,kill),
            (catch unregister(ServerName)),
            {ok,stopped}
    end.


info_connections(Ip,Port) when is_integer(Port)        ->
    ServerName = from_ip_port(Ip,Port),
    case whereis(ServerName) of
        undefined       ->
            {error,notstarted};
        Pid             ->
            Pid ! {connections, self()},
            receive
                {connections, Num} -> {ok, Num}
            end
    end.

from_ip_port(Ip,Port)                                     ->
    list_to_atom("tcpserver" ++ tuple_to_list(Ip) ++ integer_to_list(Port)).

start_server(Master,Ip,Port,ParseFun,MaxConns)         ->
    process_flag(trap_exit, true),
    case gen_tcp:listen(Port, [binary,{backlog,?BACKLOG},
                                      {ip,Ip},
                                      {packet,2},
                                      {reuseaddr,true},
                                      {active,false},
                                      {keepalive,true},
                                      {delay_send,true},
                                      {sndbuf,640000},
                                      {recbuf,640000},
%                                      {nodelay,true}]) of
                                      {nodelay,false}]) of
        {ok, Listen}  ->
            Master ! {ok, self()},
            Me = self(),
            NewChild = spawn_link(fun() -> start_child(Me,Listen,ParseFun) end),
            server_loop(Listen,NewChild,[],ParseFun,MaxConns);
        Error         ->
            Master ! {Error, self()}
    end.

start_child(Parent,Listen,ParseFun) ->
    ?DEBUG("[~p]child started, try to accept~n",[self()]),
    process_flag(trap_exit, true),
    case gen_tcp:accept(Listen) of
        {ok, Socket}    ->
            Parent ! {ok,self()},
            ?DEBUG("[~p]child accepted new connection: ~p~n",[self(),Socket]),
            inet:setopts(Socket,[binary,{packet,2},
%                                        {nodelay,true},
                                        {nodelay,false},
                                        {delay_send,true},
                                        {sndbuf,640000},
                                        {recbuf,640000},
                                        {active,false}]),
            ParseFun(Socket);
        Error           ->
            ?DEBUG("[~p]accept error: ~p~n",[self(),Error]),
            exit(Error)
    end.

server_loop(Listen,NewChild,Childs,ParseFun,MaxConns) ->
    receive
        {ok, NewChild}               ->
             Childs1 = [NewChild|Childs],
             start_another(Listen,Childs1,ParseFun,MaxConns);
        {'EXIT',NewChild,_Error}     ->
             start_another(Listen,Childs,ParseFun,MaxConns);
        {'EXIT',Child,_Error}        ->
             Childs1 = lists:delete(Child, Childs),
             case is_pid(NewChild) of
                 false      ->
                     start_another(Listen,Childs1,ParseFun,MaxConns);
                 true       ->
                     server_loop(Listen,NewChild,Childs1,ParseFun,MaxConns)
             end;
        {connections, From}          ->
            From ! {connections,length(Childs)},
            server_loop(Listen,NewChild,Childs,ParseFun,MaxConns);
        Unknown                      ->
            io:format("Unknown message:~p~n", [Unknown])
    end.

start_another(Listen,Childs,ParseFun,MaxConns) ->
    case length(Childs) of
        N when N < MaxConns  ->
            Me = self(),
            NewChild = spawn_link(fun() -> start_child(Me,Listen,ParseFun) end),
            server_loop(Listen,NewChild,Childs,ParseFun,MaxConns);
        _                    ->
            server_loop(Listen,notachild,Childs,ParseFun,MaxConns)
    end.

